﻿using System.Data.SQLite;
using MultiLang;
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.XPath;

namespace SvnRevision
{
    /// <summary>
    ///  Замена SubWCRev.exe, см. сообщение
    ///  http://gzip.rsdn.ru/forum/Message.aspx?mid=871939&only=1
    ///  
    ///  Идея: просканировать скрытые папки .svn в рабочей копии репозитория, воспользоваться
    ///  служебным файлом entries в формате XML и вытащить из него атрибут revision и выдать
    ///  его максимальное значение. Затем просканировать шаблон и заменить вхождения $WCREV$ на
    ///  номер ревизии, и сохранить полученное, только если были изменения.
    /// </summary>
    internal class Program
    {
        private static string _argumentInfo =
            ml.ml_string(1, "Требуется три аргумента: путь к рабочему каталогу, путь к шаблону, путь к выходным данным.");

        private static string _programInfo =
            "\n\r" +                                                            //MLHIDE
            ml.ml_string(6, "SvnRevision читает статус Subversion всех файлов в рабочем каталоге. ") +
            ml.ml_string(7, "Затем найденный наивысший номер ревизии используется для замены всех вхождений $WCREV в шаблоне ") +
            ml.ml_string(8, "и сохраняет его, если были внесены изменения в номер редакции.") +
            "\n\r";                                                  //MLHIDE

        private const string _replaceMark = "$WCREV$";                      //MLHIDE

        private const string _separateLine =
            "----------------------------------------------";                  //MLHIDE

        private const string _svnAspDotNetHack = "SVN_ASP_DOT_NET_HACK"; //MLHIDE
        private const string _svnDirectoryNameAspDotNetHack = "_svn";       //MLHIDE
        private const string _svnDirectoryNameCommon = ".svn";    //MLHIDE

        private const string _svnEntriesFileName = "entries"; //MLHIDE
        private const string _svnFormatFileName = "format";  //MLHIDE
        private const string _xpathRevision = @"ns:wc-entries/ns:entry/@revision"; //MLHIDE

        private const string _svnDBFileName = "wc.db";
        private static readonly string _svnDirectoryName =
            Environment.GetEnvironmentVariable(_svnAspDotNetHack) != null
                ? _svnDirectoryNameAspDotNetHack
                : _svnDirectoryNameCommon;

        [STAThread]
        private static int Main(string[] args)
        {
            try
            {
                Console.WriteLine(_programInfo);

                if (args.Length != 3)
                    throw new ArgumentException(_argumentInfo);

                var workingFolder = args[0];
                var templatePath = args[1];
                var destinationPath = args[2];

                if (!Directory.Exists(workingFolder))
                    throw new DirectoryNotFoundException();

                if (!File.Exists(templatePath))
                    throw new FileNotFoundException(string.Empty, templatePath);

                var revision = FindRevision(new DirectoryInfo(workingFolder), 0);
                Console.WriteLine(ml.ml_string(3, "Текущая ревизия {0}"), revision);

                ProcessTemplate(templatePath, destinationPath, revision);

                return 0;
            }
            catch (Exception e)
            {
                Console.WriteLine(_separateLine);
                Console.WriteLine(e);
                Console.WriteLine(_separateLine);

                return 1; // Код ошибки для студии
            }
        }

        private static void ProcessTemplate(string templatePath,
            string destinationPath, int revision)
        {
            var encoding = Encoding.Default;
            var destination = string.Empty;

            string template;

            using (var templateStream =
                new StreamReader(templatePath, encoding, true))
            {
                template = templateStream.ReadToEnd();
                encoding = templateStream.CurrentEncoding;
            }

            template = template.Replace(_replaceMark,
                revision.ToString(CultureInfo.InvariantCulture));

            if (File.Exists(destinationPath))
                destination = File.ReadAllText(destinationPath, encoding);


            if (string.Equals(template, destination, StringComparison.InvariantCulture))
                return;

            File.WriteAllText(destinationPath, template, encoding);
        }

        private static int FindRevision(DirectoryInfo directoryInfo, int maxRevision)
        {
            RevisionGetter revisionGetter = null;

            foreach (var subDir in directoryInfo.GetDirectories())
            {
                var revision = 0;

                if (string.Equals(subDir.Name, _svnDirectoryName,
                    StringComparison.InvariantCultureIgnoreCase))
                {
                    if ((revisionGetter ?? (revisionGetter = GetRevisionGetter(subDir))) != null)
                        revision = revisionGetter(subDir, maxRevision);
                }
                else
                    revision = FindRevision(subDir, maxRevision);

                if (revision > maxRevision)
                    maxRevision = revision;
            }

            return maxRevision;
        }

        private static RevisionGetter GetRevisionGetter(DirectoryInfo directoryInfo)
        {
            if (directoryInfo == null)
                throw new ArgumentNullException("directoryInfo");                 //MLHIDE

            var filePath = Path.Combine(directoryInfo.FullName, _svnFormatFileName);

            if (File.Exists(filePath))
            {
                var version = File.ReadAllText(filePath, Encoding.ASCII);

                switch (version.TrimEnd())
                {
                    case "4": return GetRevisionVer4;                                //MLHIDE
                    case "8":                                                        //MLHIDE
                    case "9": //MLHIDE
                        return GetRevisionVer8;
                    case "12": //MLHIDE
                        return GetRevisionVer12;
                }
            }
            else
            {
                // C 10 версии файл 'format' отсутсвует.
                // Пробуем прочитать версию в первой строке файла 'entries'
                filePath = Path.Combine(directoryInfo.FullName, _svnEntriesFileName);
                if (File.Exists(filePath))
                {
                    using (var sr = new StreamReader(filePath))
                    {
                        var line = sr.ReadLine();
                        int dbVer;
                        if (line != null && Int32.TryParse(line, out dbVer))
                        {
                            if (dbVer == 10)
                                return GetRevisionVer8;
                        }
                    }
                }
            }

            return delegate { return 0; };
        }

        private static int GetRevisionVer4(DirectoryInfo dir, int maxRevision)
        {
            var filePath = Path.Combine(dir.FullName, _svnEntriesFileName);

            if (!File.Exists(filePath))
                return maxRevision;

            using (var reader = new StreamReader(filePath))
            {
                var doc = new XPathDocument(reader);
                var nav = doc.CreateNavigator();

                if (nav.NameTable != null)
                {
                    var manager = new XmlNamespaceManager(nav.NameTable);
                    manager.AddNamespace("ns", "svn:");

                    var expr = nav.Compile(_xpathRevision);
                    expr.SetContext(manager);

                    var iterator = nav.Select(expr);

                    while (iterator.MoveNext())
                    {
                        try
                        {
                            if (iterator.Current != null)
                            {
                                var revision = int.Parse(iterator.Current.Value,
                                    NumberStyles.Integer, CultureInfo.InvariantCulture);

                                if (revision > maxRevision)
                                    maxRevision = revision;
                            }
                        }
                        catch (FormatException)
                        {
                        }
                    }
                }
            }

            return maxRevision;
        }

        private static int GetRevisionVer8(DirectoryInfo dir, int maxRevision)
        {
            var filePath = Path.Combine(dir.FullName, _svnEntriesFileName);

            if (!File.Exists(filePath))
                return maxRevision;

            try
            {
                using (var sr = new StreamReader(filePath))
                {
                    var lineCounter = 0;

                    string line;
                    while ((line = sr.ReadLine()) != null)
                    {
                        if (lineCounter == 3)
                        {
                            var revision = int.Parse(line,
                                NumberStyles.Integer, CultureInfo.InvariantCulture);

                            if (revision > maxRevision)
                                maxRevision = revision;

                            break;
                        }

                        lineCounter++;
                    }
                }
            }
            catch (FormatException)
            {
            }

            return maxRevision;
        }

        private static int GetRevisionVer12(DirectoryInfo dir, int maxRevision)
        {
            var filePath = Path.Combine(dir.FullName, _svnDBFileName);

            if (!File.Exists(filePath))
                return maxRevision;

            var connectionString = string.Format("Data Source={0};Read Only=True", filePath);

            using (var conn = new SQLiteConnection { ConnectionString = connectionString })
            {
                conn.Open();
                using (var cmd = conn.CreateCommand())
                {
                    cmd.CommandText = "SELECT MAX(revision) FROM nodes;";
                    maxRevision = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture);
                }
            }

            return maxRevision;
        }

        #region Nested type: RevisionGetter

        private delegate int RevisionGetter(DirectoryInfo dir, int maxRevision);

        #endregion
    }
}